﻿#region License
// (c) Intergen.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using MediaWiki.Office.Model.Utilities;
using MediaWiki.Common;

namespace MediaWiki.Office.Model
{
  public enum CellMergeType
  {
    Restart,
    Continue
  }

  public class TableCellProperties : ContentProperties
  {
    private Style _definingStyle;

    private int? _gridSpan;
    private bool? _hideCellEndMarker;
    private CellMergeType? _verticalMergeType;
    private bool? _noWrap;
    private Border _borderTop;
    private Border _borderBottom;
    private Border _borderLeft;
    private Border _borderRight;
    private Border _borderInsideHorizontal;
    private Border _borderInsideVertical;
    private CellMargins _cellMargins;
    private Shading _shading;
    private Unit? _preferredWidth;
    private VerticalAlignment? _verticalAlignment;
    private ConditionalTableStyleType? _conditionalFormatting;

    public Shading Shading
    {
      get { return _shading; }
      set { _shading = value; }
    }

    public ConditionalTableStyleType? ConditionalFormatting
    {
      get { return _conditionalFormatting; }
      set { _conditionalFormatting = value; }
    }

    public VerticalAlignment? VerticalAlignment
    {
      get { return GetValue(p => p._verticalAlignment); }
      set { _verticalAlignment = value; }
    }

    public Unit? PreferredWidth
    {
      get { return GetValue(p => p._preferredWidth); }
      set { _preferredWidth = value; }
    }

    public CellMargins CellMargins
    {
      get { return _cellMargins; }
      set { _cellMargins = value; }
    }

    public Border BorderInsideVertical
    {
      get { return _borderInsideVertical; }
      set { _borderInsideVertical = value; }
    }

    public Border BorderInsideHorizontal
    {
      get { return _borderInsideHorizontal; }
      set { _borderInsideHorizontal = value; }
    }

    public Border BorderRight
    {
      get { return _borderRight; }
      set { _borderRight = value; }
    }

    public Border BorderLeft
    {
      get { return _borderLeft; }
      set { _borderLeft = value; }
    }

    public Border BorderBottom
    {
      get { return _borderBottom; }
      set { _borderBottom = value; }
    }

    public Border BorderTop
    {
      get { return _borderTop; }
      set { _borderTop = value; }
    }

    public bool? NoWrap
    {
      get { return GetValue(p => p._noWrap); }
      set { _noWrap = value; }
    }

    public CellMergeType? VerticalMergeType
    {
      get { return GetValue(p => p._verticalMergeType); }
      set { _verticalMergeType = value; }
    }

    public bool? HideCellEndMarker
    {
      get { return GetValue(p => p._hideCellEndMarker); }
      set { _hideCellEndMarker = value; }
    }

    public int? GridSpan
    {
      get { return GetValue(p => p._gridSpan); }
      set { _gridSpan = value; }
    }

    public TableCellProperties(Style definingStyle, XElement element)
      : this(definingStyle.ParentContent, element)
    {
      if (definingStyle == null)
        throw new ArgumentNullException("definingStyle");

      _definingStyle = definingStyle;
    }

    public TableCellProperties(Content parentContent, XElement element)
      : base(parentContent)
    {
      if (element == null)
      {
        _borderTop = Border.Create(this, null, null);
        _borderBottom = Border.Create(this, null, null);
        _borderLeft = Border.Create(this, null, null);
        _borderRight = Border.Create(this, null, null);
        _borderInsideHorizontal = Border.Create(this, null, null);
        _borderInsideVertical = Border.Create(this, null, null);
        _cellMargins = new CellMargins(null, null);
        _shading = new Shading(this, null);
        return;
      }

      XNamespace ns = element.Name.Namespace;
      Table parentTable = Content.GetParentTable(parentContent);

      _gridSpan = XmlUtils.GetValue<int>(element.Element(ns + "gridSpan"));
      _hideCellEndMarker = XmlUtils.GetBoolean(element.Element(ns + "hideMark"));
      if (element.Element(ns + "vMerge") != null)
        _verticalMergeType = XmlUtils.GetValue<CellMergeType>(element.Element(ns + "vMerge")) ?? CellMergeType.Continue;
      _noWrap = XmlUtils.GetBoolean(element.Element(ns + "noWrap"));

      string conditionalValue = XmlUtils.GetValue(element.Element(ns + "cnfStyle"));
      if (conditionalValue != null)
        _conditionalFormatting = (ConditionalTableStyleType)BitUtils.ConvertToInt32(conditionalValue.Reverse());

      _borderTop = Border.Create(this,
        element.Elements(ns + "tcBorders").Elements(ns + "top").SingleOrDefault(),
        g => GetValue<object>(p => g(p._borderTop)));

      _borderLeft = Border.Create(this,
        element.Elements(ns + "tcBorders").Elements(ns + "left").SingleOrDefault(),
        g => GetValue<object>(p => g(p._borderLeft)));

      _borderBottom = Border.Create(this,
        element.Elements(ns + "tcBorders").Elements(ns + "bottom").SingleOrDefault(),
        g => GetValue<object>(p => g(p._borderBottom)));

      _borderRight = Border.Create(this,
        element.Elements(ns + "tcBorders").Elements(ns + "right").SingleOrDefault(),
        g => GetValue<object>(p => g(p._borderRight)));

      _borderInsideHorizontal = Border.Create(this,
        element.Elements(ns + "tcBorders").Elements(ns + "insideH").SingleOrDefault(),
        g => GetValue<object>(p => g(p._borderInsideHorizontal)));

      _borderInsideVertical = Border.Create(this,
        element.Elements(ns + "tcBorders").Elements(ns + "insideV").SingleOrDefault(),
        g => GetValue<object>(p => g(p._borderInsideVertical)));

      _shading = new Shading(this,
        element.Element(ns + "shd"),
        g => GetValue<object>(p => g(p._shading)));

      _cellMargins = new CellMargins(element.Element(ns + "tcMar"),
        g => GetValue<object>(p => g(p._cellMargins)) ?? ((parentTable != null) ? parentTable.Properties.GetValue(p => g(p.DefaultCellMargins)) : null));

      _preferredWidth = ModelUtils.ToUnit(XmlUtils.GetValue<int>(element.Element(ns + "tcW"), "w"),
        XmlUtils.GetValue(element.Element(ns + "tcW"), "type"));
      _verticalAlignment = XmlUtils.GetValue <VerticalAlignment>(element.Element(ns + "vAlign"));
    }

    public T GetValue<T>(Func<TableCellProperties, T> valueGetter)
    {
      if (valueGetter == null)
        throw new ArgumentNullException("valueGetter");

      Content parent = Content;

      List<Func<T>> attempts = new List<Func<T>>();

      attempts.Add(() => valueGetter(this));

      if (parent is TableCell)
      {
        TableCell parentCell = (TableCell)parent;
        TableRow parentRow = parentCell.Parent;
        Table parentTable = parentRow.Parent;

        Style parentTableStyle = parentTable.Properties.Style;

        // cell table conditional style
        if (parentCell.Properties.ConditionalFormatting != null)
          attempts.Add(() => ConditionalTableStyle.GetConditionalTableStyleValue(valueGetter,
            s => s.TableCellProperties,
            parentCell.Properties.ConditionalFormatting.Value,
            parentTableStyle.ConditionalTableStyles));

        // row table conditional style
        if (parentRow.Properties.ConditionalFormatting != null)
          attempts.Add(() => ConditionalTableStyle.GetConditionalTableStyleValue(valueGetter,
            s => s.TableCellProperties,
            parentRow.Properties.ConditionalFormatting.Value,
            parentTableStyle.ConditionalTableStyles));

        // table style
        if (parentTableStyle != null)
          attempts.Add(() => valueGetter(parentTableStyle.TableCellProperties));
      }

      return MiscellaneousUtils.EvaluateUntilValue(attempts);
    }

  }
}